# rubocop:disable Naming/MethodName

# 这个模块包含了由灯海创建的与照明相关的工具。
module DengHaiLightTools
  # DataManager 类用于管理类级别的数据存储和操作。
  class DataManager
    class << self
      # 获取或初始化存储数据的哈希表。
      def data
        @data ||= {}
      end

      def set(key, value)
        data[key] = value
      end

      def get(key)
        data[key]
      end
    end
  end

  # 为UI创建一个按钮并设置指定的参数。
  #
  # @param name [String] 按钮的名称。
  # @param tip [String] 按钮的提示信息。
  # @param text [String] 按钮的状态栏文本，默认为空字符串。
  # @param icon [String] 按钮图标的路径，默认为"#{ICON_PATH}\\no_icon.png"。
  # @param run [Proc, nil] 点击按钮时执行的代码块。
  #                        如果为nil（默认值），则点击按钮时不执行任何操作。
  # @return [UI::Command] 创建的按钮（UI命令）。
  def self.helpers_create_button(name, tip, _text = '', icon = 'no_icon.png', run = nil)
    cmd = UI::Command.new(name) do
      if run.nil?
        UI.messagebox '没有指定命令'
      else
        run.call
      end
    end
    cmd.tooltip = name
    cmd.status_bar_text = tip
    cmd.small_icon = cmd.large_icon = "#{ICON_PATH}/#{icon}"
    DataManager.data["#{name}_DHLight_Tools_CMD"] = cmd
    cmd
  end

  # 创建并显示一个 HTML 对话框
  #
  # @param [String] key 对话框的唯一标识符，用于存储和管理对话框实例
  # @param [String] title 对话框的标题
  # @param [String] html_file HTML 文件的相对路径
  # @param [Integer] width 对话框的宽度
  # @param [Integer] height 对话框的高度
  # @param [Symbol] show 是否创建后立即显示窗口，默认是
  # @return [UI::HtmlDialog] 创建或显示的 HTML 对话框实例
  def self.helpers_create_html_dialog(key, title, html_file, dialog_width, dialog_height, show = 1)
    DataManager.data[key] ||= UI::HtmlDialog.new(
      {
        dialog_title: title,
        preferences_key: "DHLIGHT_HTML_DIALOG_#{key}",
        scrollable: true,
        resizable: false,
        width: dialog_width,
        height: dialog_height,
        left: 700,
        top: 300,
        style: UI::HtmlDialog::STYLE_DIALOG
      }
    )
    DataManager.data[key].set_file(File.join(HTML_PATH, html_file))
    DataManager.data[key].show if show
    DataManager.data[key]
  end

  # 获取自定义属性
  #
  # @param [Sketchup::ComponentInstance, Sketchup::Group] obj - 要获取自定义属性的组件实例或组对象。
  # @return [Hash, nil] 包含自定义属性的哈希对象，如果未找到属性则返回 nil。
  def self.helpers_get_attribute(obj)
    t = obj.get_attribute('FixtureTools', 'LightAttribute')
    JSON.parse(t) if t
  end

  # 设置自定义属性
  #
  # @param [Sketchup::ComponentInstance] obj - 组件实例。
  # @param [String] light_name - 灯具的名称。
  # @param [String] light_category - 灯具的类别。
  # @param [String] manufacturer - 灯具的生产厂家。
  # @param [Integer] channel_count - 灯具的通道数。
  # @param [String] fixture_id - 灯具的配接ID（Fixture ID）。
  # @param [String] universe - 灯具所在的宇宙。
  # @param [Integer] dmx_address - 灯具的DMX地址。
  # @return [Hash] 设置成功的属性。
  def self.helpers_set_attribute(obj, light_name, light_category, manufacturer = '重庆灯海', channel_count = 1, fixture_id = 1, universe = 1, dmx_address = 1, author = '重庆灯海文化', description = '')
    attr_obj = {
      light_name: light_name,
      light_category: light_category,
      manufacturer: manufacturer,
      channel_count: channel_count,
      fixture_id: fixture_id,
      universe: universe,
      dmx_address: dmx_address,
      author: author,
      description: description
    }
    obj.set_attribute('FixtureTools', 'LightAttribute', attr_obj.to_json)
  end

  # 设置给定对象的自定义属性为提供的哈希对象。
  #
  # @param obj [Sketchup::Entity] 要设置属性的对象。
  # @param hash_obj [Hash] 包含属性信息的哈希对象。
  # @return [Sketchup::Entity] 设置后的对象
  def self.helpers_set_attribute_arry(obj, hash_obj)
    obj.set_attribute('FixtureTools', 'LightAttribute', hash_obj.to_json)
  end

  # 复制自定义属性
  #
  # @return [Hash, nil] 包含复制的自定义属性的哈希对象，如果源属性未找到则返回 nil。
  def self.helpers_copy_attribute(obj1, obj2)
    obj2.set_attribute('FixtureTools', 'LightAttribute', helpers_get_attribute(obj1).to_json)
  end

  # 载入文件，包括检查错误
  def self.load_skp_file(file_path)
    begin
      loaded = Sketchup.active_model.definitions.load(file_path)
    rescue StandardError => e
      UI.messagebox("载入文件错误，请检查是否损坏！\n#{e}")
      return nil
    end
    loaded
  end

  # 将字符串压缩为 ZIP 文件。
  #
  # @param [String] xml 要压缩的数据，以字符串形式提供。
  # @param [String] output_file 压缩后数据将保存为 ZIP 文件的输出路径。
  def self.help_export_mvr(obj_list, output_file)
    p "help_export_mvr 收到的 xml 是：#{obj_list}"
    xml = to_xml(obj_list)

    return if xml.nil?

    Zip::OutputStream.open(output_file) do |zipfile|
      # 添加一个新的条目到zip文件
      zipfile.put_next_entry('GeneralSceneDescription.xml')
      zipfile.write(xml)
    end

    UI.messagebox("导出MVR文件完成，位置在：\n#{output_file}")
  end

  # 阻止修改,必须是监听调用
  class DontEditIt
    def onOpen(_instance)
      UI.messagebox('修改和炸开灯具模型可能会导致灯具失效。')
      Sketchup.active_model.close_active
      # Sketchup.undo
    end
  end

  # 转换xml
  def self.to_xml(data_list)
    obj_mubmer = 0
    xml_str = '<?xml version="1.0" encoding="UTF-8" standalone="no" ?><GeneralSceneDescription verMajor="1" verMinor="4"><Scene><Layers><Layer name="None"><ChildList>'
    data_list.each do |entity|
      next unless entity.is_a?(Sketchup::ComponentInstance) && helpers_get_attribute(entity)

      obj_mubmer += 1
      eid = helpers_get_attribute(entity)
      transformation = entity.transformation
      x = Geom::Vector3d.new(transformation.xaxis)
      y = Geom::Vector3d.new(transformation.yaxis)
      z = Geom::Vector3d.new(transformation.zaxis)
      px = transformation.origin.x.to_mm
      py = transformation.origin.y.to_mm
      pz = transformation.origin.z.to_mm
      xml_str += "<Fixture><Matrix>{#{x[0].round(5)},#{x[1].round(5)},#{x[2].round(5)}}{#{y[0].round(5)},#{y[1].round(5)},#{y[2].round(5)}}{#{z[0].round(5)},#{z[1].round(5)},#{z[2].round(5)}}{#{px.round(5)},#{py.round(5)},#{pz.round(5)}}</Matrix>"
      xml_str += "<GDTFSpec>#{eid['light_name']}</GDTFSpec><GDTFMode>#{eid['channel_count']}ch</GDTFMode><Addresses><Address break=\"0\">#{((eid['universe'] - 1) * 512) + eid['dmx_address']}</Address></Addresses></Fixture>"
    end
    xml_str += '</ChildList></Layer></Layers></Scene></GeneralSceneDescription>'
    if obj_mubmer.zero?
      xml_str = nil
      UI.messagebox('没有可导出的灯具。')
    end
    xml_str
  end

  # 日志记录类
  # 实现功能：不在每次调用的时候创建新文件。只有开始写入的时候才创建日志文件。
  class LazyLogger
    def initialize(log_file)
      @log_file = log_file
      @logger = nil
    end

    def logger
      @logger ||= create_logger
    end

    private

    def create_logger
      new_logger = Logger.new(nil) # 创建一个空的 Logger
      new_logger.formatter = proc { |severity, datetime, _progname, message|
        "#{severity} [#{datetime}] - #{message}\n"
      }
      new_logger
    end

    def create_file_and_logger
      @logger = Logger.new(@log_file, 'daily')
      @logger.formatter = logger.formatter
      @logger
    end

    def method_missing(method_name, *args, &block)
      if logger.respond_to?(method_name)
        create_file_and_logger.send(method_name, *args, &block)
      else
        super
      end
    end

    def respond_to_missing?(method_name, include_private = false)
      logger.respond_to?(method_name) || super
    end
  end

  # 检查更新的类
  class UpdateChecker
    # 创建一个队列，用于在新线程和主线程之间传递消息
    @message_queue = Queue.new

    class << self
      attr_reader :message_queue
    end

    # 读取保存的推迟更新的时间
    # 如果大于1天（只计算日期差），就返回true，否则返回false
    # 如果文件不存在，也返回true
    def self.need_update?
      return true unless File.exist?(BLOCK_FILE)

      recorded_time = nil
      File.open(BLOCK_FILE, 'r') do |file|
        recorded_time = Time.parse(file.readline.chomp)
      end
      current_time = Time.now
      days_difference = (current_time - recorded_time).to_i / (24 * 3600) # 计算相隔的天数
      days_difference >= 1
    end

    # 开启新线程，执行检查更新
    def self.update
      return unless need_update?

      Thread.new do
        remote = get_final_url(UPDATE_URL)
        remote_version = remote.match(%r{/(\d+\.\d+\.\d+)$})[1] if remote # 获取发布的最新版本
        local_json = JSON.parse(File.read(LOCAL_VERSION_PATH))['version'] # 本地版本
        if new_versions?(remote_version, local_json) # 如果有更新
          # 获取更新rbz的地址，不知道以后会不会变。
          # 这里采用递归的方式获取，应该能拿到最终下载地址。
          want_file_url = "https://gitee.com/akiWang/LightToolsForSketchup/releases/download/#{remote_version}/LightTools_For_Sketchup_#{remote_version}.rbz"
          file_path = download_file(get_final_url(want_file_url))
          @message_queue.push({ action: 'file_downloaded', file_path: file_path }) if file_path
        else
          # 没有更新，停止timer
          @message_queue.push({ action: 'stop_timer' })

        end
      rescue StandardError => e
        @message_queue.push({ action: 'thread_error', message: e }) # 捕获子线程错误，提交给主线程
      end
    end

    # 下载文件到临时文件目录中
    def self.download_file(url)
      in_url = URI(url)
      response = Net::HTTP.get_response(in_url)
      retuen nil if response.body.empty? # 下载的文件为空。确认是否需要做这个判断。 另外，是否需要做MD5验证？？？
      temp_file = Tempfile.new(['LightTools', '.rbz']) # 创建临时文件
      temp_file.binmode
      temp_file.write(response.body)
      temp_file.close # 关闭临时文件
      temp_file.path
    end

    # 递归方式获得真实最终地址
    def self.get_final_url(url)
      uri = URI(url)
      response = Net::HTTP.get_response(uri)
      if response.code.to_i == 301 || response.code.to_i == 302
        # 处理重定向
        new_location = response['location']
        get_final_url(new_location) # 递归调用，处理新的重定向
      elsif response.is_a?(Net::HTTPClientError) || response.is_a?(Net::HTTPServerError)
        show_error_message(response) # 显示错误信息
        nil
      else
        uri.to_s
      end
    end

    # 比较版本号
    # 如果 true 则 version2 比 version1 更新
    def self.new_versions?(version1, version2)
      return false if version1.nil? || version2.nil?

      parts1 = version1.split('.').map(&:to_i)
      parts2 = version2.split('.').map(&:to_i)
      parts1.zip(parts2).each do |part1, part2|
        return true if part1 > part2
        return false if part1 < part2
      end
      false # 默认返回 false，表示版本号相同
    end

    # 这里要把错误信息由主线程处理。主线程应该弹出一个强提醒。
    # 如果是意外的错误，是否会影响最终用户体验？
    def self.show_error_message(response)
      @message_queue.push({ action: 'has_error', error_message: "【重庆灯海灯光工具】检查更新失败。\n错误信息: #{response.code} - #{response.message}" })
      LOGGER.error("检查更新失败：#{response.code} - #{response.message}")
    end

    # 安装扩展
    def self.do_install(file_path)
      s = true
      original_stdout = $stdout
      $stdout = File.new(File::NULL, 'w') # 重定向错误输出口，不显示错误
      begin
        Sketchup.install_from_archive(file_path)
      rescue Interrupt
        $stdout = original_stdout # 恢复错误输出口
        # 是否需要提示用户取消了安装？会不会太繁琐？
        UI.messagebox("【重庆灯海灯光工具】提醒：\n\n你拒绝了插件的更新，我们建议保持最新版的插件，不定期会有新的功能加入或者漏洞的修复。\n如果需要再次更新，请重启Sketckup。")
        LOGGER.info('用户取消了更新安装')
        s = false
      end
      UI.messagebox('更新【灯海灯光工具】插件完成，请重启Sketckup启用。') if s
    end

    # 右下角显示通知
    def self.show_notification(rbz_file, message)
      ex = SketchupExtension.new('重庆灯海灯光工具111', rbz_file)
      notification = UI::Notification.new(ex, message)
      notification.on_accept('现在更新') do
        do_install(rbz_file)
      end
      notification.on_dismiss('明天再说') do
        # 点了推迟以后记录当前时间，推迟1天以后再次提示。
        File.binwrite(BLOCK_FILE, Time.now)
      end
      notification.show
    end

    # 主线程中处理队列中的消息
    if need_update? # 这里处理下，如果不需要更新，就不开启定时器
      timer_id = UI.start_timer(0.1, true) do
        unless UpdateChecker.message_queue.empty?
          message_data = UpdateChecker.message_queue.pop
          case message_data[:action]
          when 'file_downloaded' # 下载完成
            show_notification(message_data[:file_path], "【灯海灯光工具】新版本下载完毕，是否继续安装？\n如果选择更新，请`允许`接下来的未知签名提醒。")
            UI.stop_timer(timer_id)
          when 'has_error'
            UI.messagebox(message_data[:error_message])
            UI.stop_timer(timer_id)
          when 'stop_timer'
            UI.stop_timer(timer_id)
          when 'thread_error'
            p "子线程报错： #{message_data[:message]}"
            # "子线程报错：#{__FILE__} >> #{message_data[:message].backtrace.first} #{message_data[:message].message}"
            LOGGER.error(DengHaiLightTools.logger_text(message_data[:message].backtrace.first, message_data[:message].message))
          end
        end
      end
    end
  end
end
# rubocop:enable Naming/MethodName
